package com.google.code.sms;

import com.google.code.sms.util.BoundedList;
import com.google.code.sms.service.SettingsService;
import org.apache.commons.lang.exception.*;

import java.io.*;
import java.text.*;
import java.util.*;

/**
 * Logger implementation which logs to SMS_HOME/sms.log.
 *
 * @author Scott Ware
 */
public class Logger {

    private String category;
    private static List<Entry> entries = Collections.synchronizedList(new BoundedList<Entry>(50));
    private static PrintWriter writer;

    /**
     * Creates a logger for the given class.
     *
     * @param name The name.
     * @return A logger for the class.
     */
    public static Logger getLogger(String className) {
        return new Logger(className);
    }

    /**
     * Returns the last few log entries.
     *
     * @return The last few log entries.
     */
    public static Entry[] getLatestLogEntries() {
        return entries.toArray(new Entry[0]);
    }

    private Logger(String name) {
        int lastDot = name.lastIndexOf('.');
        if (lastDot == -1) {
            category = name;
        } else {
            category = name.substring(lastDot + 1);
        }
    }

    /**
     * Logs a debug message.
     *
     * @param message The log message.
     */
    public void debug(Object message) {
        debug(message, null);
    }

    /**
     * Logs a debug message.
     *
     * @param message The message.
     * @param error The optional exception.
     */
    public void debug(Object message, Throwable error) {
        add(Level.DEBUG, message, error);
    }

    /**
     * Logs an info message.
     *
     * @param message The message.
     */
    public void info(Object message) {
        info(message, null);
    }

    /**
     * Logs an info message.
     *
     * @param message The message.
     * @param error The optional exception.
     */
    public void info(Object message, Throwable error) {
        add(Level.INFO, message, error);
    }

    /**
     * Logs a warning message.
     *
     * @param message The message.
     */
    public void warn(Object message) {
        warn(message, null);
    }

    /**
     * Logs a warning message.
     *
     * @param message The message.
     * @param error The optional exception.
     */
    public void warn(Object message, Throwable error) {
        add(Level.WARN, message, error);
    }

    /**
     * Logs an error message.
     *
     * @param message The message.
     */
    public void error(Object message) {
        error(message, null);
    }

    /**
     * Logs an error message.
     *
     * @param message The message.
     * @param error The optional exception.
     */
    public void error(Object message, Throwable error) {
        add(Level.ERROR, message, error);
    }

    private void add(Level level, Object message, Throwable error) {
        Entry entry = new Entry(category, level, message, error);
        try {
            getPrintWriter().println(entry);
        } catch (IOException x) {
            System.err.println("Failed to write to sms.log.");
        }
        entries.add(entry);
    }

    private static synchronized PrintWriter getPrintWriter() throws IOException {
        if (writer == null) {
            writer = new PrintWriter(new FileWriter(getLogFile(), false), true);
        }
        return writer;
    }

    public static File getLogFile() {
        File smsHome = SettingsService.getSMSHome();
        return new File(smsHome, "sms.log");
    }

    /**
     * Log level.
     */
    public enum Level {

        DEBUG, INFO, WARN, ERROR
    }

    /**
     * Log entry.
     */
    public static class Entry {

        private String category;
        private Date date;
        private Level level;
        private Object message;
        private Throwable error;
        private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");

        public Entry(String category, Level level, Object message, Throwable error) {
            this.date = new Date();
            this.category = category;
            this.level = level;
            this.message = message;
            this.error = error;
        }

        public String getCategory() {
            return category;
        }

        public Date getDate() {
            return date;
        }

        public Level getLevel() {
            return level;
        }

        public Object getMessage() {
            return message;
        }

        public Throwable getError() {
            return error;
        }

        @Override
        public String toString() {
            StringBuilder buf = new StringBuilder();
            buf.append('[').append(DATE_FORMAT.format(date)).append("] ");
            buf.append(level).append(' ');
            buf.append(category).append(" - ");
            buf.append(message);

            if (error != null) {
                buf.append('\n').append(ExceptionUtils.getFullStackTrace(error));
            }
            return buf.toString();
        }
    }
}